package engine.util.draw;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;

import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import engine.gameObject.collider.BoundingSphere;
import engine.gameObject.collider.SphereCollider;
import engine.gameObject.model.Model;
import engine.material.DefaultMaterial;

public class Sphere extends Model {

	public Sphere(float radius, int slices, int stacks) {
		super(getIndexpointer(radius, slices, stacks), getVAOID(radius, slices,
				stacks), new DefaultMaterial(), 0, true, getTriangleCount(
				slices, stacks));

		setBoundingRadius(radius);
		
		SphereCollider collider = new SphereCollider();
		collider.addSphere(new BoundingSphere(radius, new float[]{0, 0, 0}));
		setCollider(collider);
	}

	private static int getIndexpointer(float radius, int slices, int stacks) {
		IntBuffer indexBuffer = BufferUtils.createIntBuffer(getTriangleCount(
				slices, stacks));

		// create indices
		for (int i = 0; i < stacks; i++) {
			int baseIndexTop = i * (slices + 1);
			int baseIndexBottom = (i + 1) * (slices + 1);

			for (int j = 0; j < slices; j++) {
				// first triangle
				indexBuffer.put(j + baseIndexBottom);
				indexBuffer.put((j + 1) + baseIndexBottom);
				indexBuffer.put(j + baseIndexTop);

				// second triangle
				indexBuffer.put(j + baseIndexTop);
				indexBuffer.put((j + 1) + baseIndexBottom);
				indexBuffer.put((j + 1) + baseIndexTop);
			}
		}

		indexBuffer.rewind();

		int indexPointer = glGenBuffers();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexPointer);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL_STATIC_DRAW);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

		return indexPointer;
	}

	private static int getVAOID(float radius, int slices, int stacks) {
		int numVertices = (stacks + 1) * (slices + 1);
		
		FloatBuffer buffer = BufferUtils.createFloatBuffer(numVertices * 8);

		// add slice vertices
		for (int i = 0; i <= stacks; i++) {
			float angleVertical = (float) i / (float) stacks * (float) Math.PI;
			float curRadius = radius * (float) Math.sin(angleVertical);
			float curHeight = radius * (float) Math.cos(angleVertical);
			
			for (int j = 0; j <= slices; j++) {
				float angleHorizontal = (float) j / (float) slices * 2f
						* (float) Math.PI;
				
				float x = (float) Math.cos(angleHorizontal) * curRadius;
				float y = curHeight;
				float z = (float) Math.sin(angleHorizontal) * curRadius;
				
				buffer.put(x);
				buffer.put(y);
				buffer.put(z);
				
				buffer.put(angleHorizontal / (float)Math.PI / 2);
				buffer.put(angleVertical / (float)Math.PI);
				
				float length = (float)Math.sqrt(x * x + y * y + z * z);
				buffer.put(x / length);
				buffer.put(y / length);
				buffer.put(z / length);
			}
		}

		//make vao out of vertices, normals and texcoords. 
		buffer.rewind();
		
		int stride = (3 + 2 + 3) * 4;

		int vaoID = glGenVertexArrays();
		glBindVertexArray(vaoID);

		int vboID = glGenBuffers();
		glBindBuffer(GL_ARRAY_BUFFER, vboID);
		glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);

		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0);
		
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 12);
		
		glEnableVertexAttribArray(2);
		glVertexAttribPointer(2, 3, GL_FLOAT, false, stride, 20);

		glBindBuffer(GL_ARRAY_BUFFER, 0);

		glBindVertexArray(0);

		return vaoID;
	}

	private static int getTriangleCount(int slices, int stacks) {
		return stacks * slices * 2 * 3;
	}
}
